SuperStrict

Rem
bbdoc: Remote actors (threaded only)
about:
Remote actors use socket streams and 
(de-)serialization of message 
objects to allow messages between
programs -- even between computers.

The host program should implement 
#TServerActor, which does the actual 
work. The client program should 
implement #TProxyActor -- compatible 
with the same serialization syntax!
End Rem
Module Otus.RemoteActor

ModuleInfo "Version: 0.10"
ModuleInfo "Author: Jan Varho"
ModuleInfo "License: Public Domain"
ModuleInfo "Copyright: Jan Varho (jan@varho.org)"

?threaded

Import BRL.SocketStream

Import Otus.Actor

Rem
bbdoc: A proxy for a remote actor
about:
A proxy actor redirects messages to 
a remote actor (a TServerActor) via
a socket stream. Set the socket 
stream using #SetSocketStream.

You must implement #Serialize and 
#Deserialize and have them be 
compatible with the remote actor.
End Rem
Type TProxyActor Extends TActor Abstract
	
	Rem
	bbdoc: Deserializes a result object
	returns: A result object
	about:
	You must implement this in a derived class.
	End Rem	
	Method Deserialize:Object(msg:String) Abstract
	
	Method Process:Int()
		_mutex.Lock
		Local msg:String[] = _GetFromSocket()
		If Not msg
			_mutex.Unlock
			Return False
		End If
		
		For Local m:TMessage = EachIn _queue
			If m._msg.Compare(msg[0]) Continue
			
			_queue.Remove m
			_mutex.Unlock
			m._SetDone Deserialize(msg[1])
			Return True
		Next
		_mutex.Unlock
		
		Assert False Else "Invalid message received"
		Return False
	End Method
	
	Method Send:TMessage(msg:Object)
		Local s:String = Serialize(msg)
		Local m:TMessage = Super.Send(s)
		
		_mutex.Lock
		_sstream.WriteInt(s.length)
		_sstream.WriteString(s)
		_mutex.Unlock
		
		Return m
	End Method
	
	Rem
	bbdoc: Serializes a message object
	returns: A string
	about:
	You must implement this in a derived class.
	End Rem
	Method Serialize:String(msg:Object) Abstract
	
	Rem
	bbdoc: Set socket stream
	about:
	This will be used for communication with a 
	remote actor. Must be connected.
	End Rem
	Method SetSocketStream(s:TSocketStream)
		_mutex.Lock
		_sstream = s
		_mutex.Unlock
	End Method
	
	' Not called
	Method Receive:Object(msg:Object)
		Throw "A proxy actor doesn't receive messages"
	End Method
	
	' Private
	
	Field _sstream:TSocketStream
	
	Method _GetFromSocket:String[]()
		If Not SocketReadAvail(_sstream._socket) Then Return Null
		Local msg:String = _sstream.ReadString( _sstream.ReadInt() )
		Local res:String = _sstream.ReadString( _sstream.ReadInt() )
		Return [msg, res]
	End Method
	
End Type

Rem
bbdoc: An actor accepting remote messages
about:
A server actor accepts remote messages via
a socket stream. You can set it using 
#SetSocketStream.

You must implement #Receive, #Serialize and
#Deserialize in a derived class.
End Rem
Type TServerActor Extends TActor Abstract
	
	Rem
	bbdoc: Deserializes a message object
	returns: A message object
	about:
	You must implement this in a derived class.
	End Rem	
	Method Deserialize:Object(msg:String) Abstract
	
	Method Process:Int()
		_mutex.Lock
		Local msg:String = _GetFromSocket()
		_mutex.Unlock
		If Not msg Return False
		
		Local res:String = Serialize( Receive( Deserialize(msg) ) )
		
		_mutex.Lock
		_sstream.WriteInt msg.length
		_sstream.WriteString msg
		_sstream.WriteInt res.length
		_sstream.WriteString res
		_mutex.Unlock
		
		Return True
	End Method
	
	Rem
	bbdoc: Receive a message
	returns: The result of the message
	about:
	This method should not be called by the user.
	Instead, it needs to be implemented in inheriting
	types and is called internally. Use #Process to
	receive and process messages.
	End Rem
	Method Receive:Object(msg:Object) Abstract
	
	Method Send:TMessage(msg:Object)
		Throw "A server actor doesn't accept local messages"
	End Method
	
	Rem
	bbdoc: Serializes a result object
	returns: A string
	about:
	You must implement this in a derived class.
	End Rem
	Method Serialize:String(msg:Object) Abstract
	
	Rem
	bbdoc: Set socket
	about:
	This will be used for communication with a 
	remote actor. Must be connected.
	
	If autoClose is set, the socket will be closed
	after the actor is deleted.
	End Rem
	Method SetSocketStream(s:TSocketStream)
		_mutex.Lock
		_sstream = s
		_mutex.Unlock
	End Method
	
	' Private
	
	Field _sstream:TSocketStream
		
	Method _GetFromSocket:String()
		If Not SocketReadAvail(_sstream._socket) Return Null
		Local msg:String = _sstream.ReadString( _sstream.ReadInt() )
		Return msg
	End Method
	
End Type

?